﻿using Devil;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.Rendering;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;

namespace GameToolkit.Editor
{
    [System.Serializable]
    public struct CustomLightMode
    {
        public string lightMode;
        public PassType passType;
    }

    [System.Serializable, ProjectConfig(Title = "Game Toolkit/Strip Shaders")]
    public class StripShadersUtil : ProjectConfiguration.Configurable
    {
        [WrapExtenal(typeof(LocalKeyword))]
        public static Wrapper<Shader, uint> GetShaderKeywordCount;

        [WrapExtenal(typeof(LocalKeyword))]
        public static Wrapper<Shader, string, uint> GetShaderKeywordIndex;

        //TODO Is Building AB
        public static bool IsBuildingAb => false;

        static ParallelDispatcher.Handler sCurrentCmd;

        static readonly string[] SHADER_ACTIONS = {
            "Shader Actions",
            "Get Shaders As Sorted",
            "Sort Material By Shaders",
            "Load Material Variants",
            "Collect Shader Variants",
            "Clear Invalid Materials' Property",
            //"Get Build Shaders",
        };

        const int ACT_GET_SHADER = 1;
        const int ACT_SORT_MATERIAL = 2;
        const int ACT_LOAD_MATERIAL = 3;
        const int ACT_COLLECT_VARIANT = 4;
        const int ACT_CLEAR_INVALID_PROPS = 5;
        //const int ACT_GET_BUILD_SHADERS = 6;

        public static PassIdentifier GetPassID(uint pass)
        {
            object id = new PassIdentifier();
            var flag = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
            var tp = typeof(PassIdentifier);
            var field = tp.GetField("m_PassIndex", flag);
            field.SetValue(id, pass);
            return (PassIdentifier)id;
        }

        public static PassIdentifier GetPassID(uint subShader, uint pass)
        {
            object id = new PassIdentifier();
            var flag = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
            var tp = typeof(PassIdentifier);
            var field = tp.GetField("m_SubShaderIndex", flag);
            field.SetValue(id, subShader);
            field = tp.GetField("m_PassIndex", flag);
            field.SetValue(id, pass);
            return (PassIdentifier)id;
        }


        public static bool IsValidKeyword(Shader shader, string keyword)
        {
            if (shader == null || string.IsNullOrEmpty(keyword))
                return false;
            var count = GetShaderKeywordCount(shader);
            var index = GetShaderKeywordIndex(shader, keyword);
            return index < count;
            //var lk = new LocalKeyword(shader, keyword);
            //return lk.isValid;
            //ProcessKeywords(shader);
            //var id = MathExt.Hash(shader.GetInstanceID(), keyword.GetHashCode());
            //return _keywordsCache.Contains(id);
        }

        public static string GetCurrentPipeline()
        {
            var tp = GraphicsSettings.renderPipelineAsset == null ? null : GraphicsSettings.renderPipelineAsset.GetType().Name;
            switch (tp)
            {
                case "UniversalRenderPipelineAsset":
                    return "UniversalPipeline";
                default:
                    return null;
            }
        }


        [MenuItem("CONTEXT/Shader/Find Using Materials")]
        static void FindMaterials(MenuCommand context)
        {
            var shader = context.context as Shader;
            if (shader == null || sCurrentCmd.IsAlive)
                return;
            var cmd = new FindUsingMaterialsCmd(shader);
            sCurrentCmd = cmd.Schedule(true);

        }


        readonly static PassType Invalid_PassType = (PassType)(-1);


        public PassType GetPassType(ShaderData.Pass pass)
        {
            var type = pass.FindTagValue(new ShaderTagId("LightMode"));
            Debug.LogFormat("{0}-{1}", pass.Name, type.name);
            return GetPassType(type.name);
        }


        Dictionary<string, PassType> mPassMap;
        void InitPassMap()
        {
            if (mPassMap == null)
                mPassMap = new Dictionary<string, PassType>();
            else
                mPassMap.Clear();
            mPassMap["UniversalForward"] = PassType.ScriptableRenderPipeline;
            mPassMap["UniversalGBuffer"] = PassType.ScriptableRenderPipeline;
            mPassMap["DepthOnly"] = PassType.ScriptableRenderPipeline;
            mPassMap["DepthNormals"] = PassType.ScriptableRenderPipeline;
            mPassMap["SRPDefaultUnlit"] = PassType.ScriptableRenderPipelineDefaultUnlit;
            mPassMap["FORWARDBASE"] = PassType.ForwardBase;
            mPassMap["FORWARDADD"] = PassType.ForwardAdd;
            mPassMap["DEFERRED"] = PassType.Deferred;
            mPassMap["META"] = PassType.Meta;
            mPassMap["SHADOWCASTER"] = PassType.ShadowCaster;
            mPassMap["ShadowCaster"] = PassType.ShadowCaster;
            mPassMap["Vertex"] = PassType.Vertex;
            mPassMap["VertexLM"] = PassType.VertexLM;
#if !UNITY_2022_3_OR_NEWER
            mPassMap["PREPASSBASE"] = PassType.LightPrePassBase;
            mPassMap["PREPASSFINAL"] = PassType.LightPrePassFinal;
#endif
            mPassMap["Always"] = PassType.Normal;
            if (m_CustomLightModes != null)
            {
                for (int i = 0; i < m_CustomLightModes.Count; i++)
                {
                    var mod = m_CustomLightModes[i];
                    if (!string.IsNullOrEmpty(mod.lightMode))
                        mPassMap[mod.lightMode] = mod.passType;
                }
            }
        }

        PassType GetPassType(string lightmode)
        {
            if (string.IsNullOrEmpty(lightmode))
                return PassType.Normal;
            PassType ptype;
            if (mPassMap != null && mPassMap.TryGetValue(lightmode, out ptype))
                return ptype;
            return Invalid_PassType;
        }

        readonly string[] mDefaultFolders = new string[] { "Assets/" };

        //public bool m_StripUnusedShaders;
        //public string m_ExcludeShahders;

        List<Shader> mAlwaysIncludeShaders = new List<Shader>();

        [Header("Custom Defination")]
        public bool m_AllowStripShaderBuild;

        public List<CustomLightMode> m_CustomLightModes;
        [Mask(typeof(PassType), IsIndexEnum = true)]
        public int m_CollectPassTypes =
            1 << (int)PassType.Normal
            | 1 << (int)PassType.ScriptableRenderPipeline
            | 1 << (int)PassType.ScriptableRenderPipelineDefaultUnlit
            | 1 << (int)PassType.ShadowCaster;


        [Header("Shader Rules"), Tooltip("保持不被剔除的变体")]
        public string[] m_KeepShaderVairants;
        [Tooltip("保持不被剔除的着色器")]
        public Shader[] m_KeepShadersForBuild;
        //[FormerlySerializedAs("m_IncludeShadersForBuild"), Tooltip("收集着色器时匹配包含着色器名称的正则表达式")]
        //public string[] m_IncludeShaderPatterns;
        [FormerlySerializedAs("m_ExcludeShadersForBuild"), Tooltip("收集着色器时匹配不包含着色器名称的正则表达式")]
        public string[] m_ExcludeShaderPatterns;

        public Shader[] m_SortingShaders;
        public int m_SortingShaderOffset = -10;

        //[HideInInspector]
        //[Header("着色器排序")]
        //public string m_SortShaderFolder;

        //public List<Shader> m_SortShaders;

        [Header("Material Rules"), Tooltip("搜索材质所在的文件夹")]
        public string[] m_MaterialFolders;

        [Tooltip("排除的材质文件或文件夹的正则表达式")]
        public string[] m_ExcludeMaterialPatterns;

        [Header("Build Data"), Tooltip("打包可执行程序时，是否剔除已经包含在 ShaderVariantCollection 中的着色器")]
        public bool m_StripShadersForPlayer;

        [HideInInspector, Tooltip("保存着色器变体的资源")]
        public ShaderVariantCollection m_VariantCollection;

        Shader mShader;
        Material mMaterial;
        string mShaderInfo;
        HashSet<string> mBuildShaders;


        void GetShadersForBuild(bool alwaysIncludeShaders)
        {
            Debug.LogFormat("Get Shaders For Build ({0}).", alwaysIncludeShaders ? "Always" : "Variants");
            if (mBuildShaders == null)
                mBuildShaders = new HashSet<string>();
            else
                mBuildShaders.Clear();
            if (alwaysIncludeShaders)
            {
                for (int i = 0; i < mAlwaysIncludeShaders.Count; i++)
                {
                    var s = mAlwaysIncludeShaders[i];
                    if (s != null)
                        mBuildShaders.Add(s.name);
                }
            }
            if(!m_StripShadersForPlayer || !alwaysIncludeShaders)
            {
                if (m_KeepShadersForBuild != null)
                {
                    for (int i = 0; i < m_KeepShadersForBuild.Length; i++)
                    {
                        var s = m_KeepShadersForBuild[i];
                        if (s != null)
                            mBuildShaders.Add(s.name);
                    }
                }
                if (m_VariantCollection != null)
                {
                    var ser = new SerializedObject(m_VariantCollection);
                    var shaders = ser.FindProperty("m_Shaders");
                    for (int i = 0; i < shaders.arraySize; i++)
                    {
                        var shader = shaders.GetArrayElementAtIndex(i).FindPropertyRelative("first").objectReferenceValue as Shader;
                        if (shader != null)
                            mBuildShaders.Add(shader.name);
                    }
                }
            }
        }

        bool ImportMaterial(string matPath)
        {
            if (m_ExcludeMaterialPatterns == null)
                return true;
            for (int i = 0; i < m_ExcludeMaterialPatterns.Length; i++)
            {
                var rule = m_ExcludeMaterialPatterns[i];
                if (!string.IsNullOrEmpty(rule) && Regex.IsMatch(matPath, rule, RegexOptions.IgnoreCase))
                    return false;
            }
            return true;
        }

        bool ImportShader(string shaderName)
        {
            if (m_ExcludeShaderPatterns != null && m_ExcludeShaderPatterns.Length > 0)
            {
                for (int i = 0; i < m_ExcludeShaderPatterns.Length; i++)
                {
                    if (!string.IsNullOrEmpty(m_ExcludeShaderPatterns[i])
                        && Regex.IsMatch(shaderName, m_ExcludeShaderPatterns[i], RegexOptions.IgnoreCase))
                        return false;
                }
            }
            //if (m_IncludeShaderPatterns != null && m_IncludeShaderPatterns.Length > 0)
            //{
            //    for (int i = 0; i < m_IncludeShaderPatterns.Length; i++)
            //    {
            //        if (!string.IsNullOrEmpty(m_IncludeShaderPatterns[i])
            //            && Regex.IsMatch(shaderName, m_IncludeShaderPatterns[i]))
            //            return true;
            //    }
            //    return false;
            //}
            return true;
        }

        public bool IsShaderForBuild(string shaderName)
        {
            if (mBuildShaders == null)
            {
                GetShadersForBuild(!IsBuildingAb);
            }
            return mBuildShaders.Contains(shaderName);
            //if (!MakeAB.IsBuildingAsset && m_StipAllShaderForPlayer)
            //    return false;

        }

        void GetAlwaysIncludeShaders()
        {
            var obj = GraphicsSettings.GetGraphicsSettings();
            var ser = new SerializedObject(obj);
            var shaders = ser.FindProperty("m_AlwaysIncludedShaders");
            mAlwaysIncludeShaders.Clear();
            for (int i = 0; i < shaders.arraySize; i++)
            {
                var shader = shaders.GetArrayElementAtIndex(i).objectReferenceValue as Shader;
                if (shader != null)
                    mAlwaysIncludeShaders.Add(shader);
            }
            ser.Dispose();
        }

        public override void OnEnableGUI()
        {
            base.OnEnableGUI();
            mBuildShaders = null;
            GetAlwaysIncludeShaders();
        }

        public override void OnDisableGUI()
        {
            base.OnDisableGUI();
            mBuildShaders = null;
        }

        public override void OnGUI()
        {
            base.OnGUI();

            EditorGUILayout.BeginHorizontal();
            m_VariantCollection = EditorGUILayout.ObjectField("Shader Variants", m_VariantCollection,
                typeof(ShaderVariantCollection), false) as ShaderVariantCollection;
            EditorGUI.BeginDisabledGroup(sCurrentCmd.IsAlive);
            //var loadMats = GUILayout.Button("Load Material Variants", GUILayout.Width(160));
            var shader_act = EditorGUILayout.Popup(0, SHADER_ACTIONS, GUILayout.Width(160));
            EditorGUI.EndDisabledGroup();
            EditorGUILayout.EndHorizontal();

            //EditorGUILayout.BeginHorizontal();
            //mShader = EditorGUILayout.ObjectField("Shader", mShader, typeof(Shader), false) as Shader;
            //var getShaderInfo = GUILayout.Button("Update Shader Info") && mShader != null;
            //EditorGUILayout.EndHorizontal();

            //EditorGUILayout.BeginHorizontal();
            //mMaterial = EditorGUILayout.ObjectField("Material", mMaterial, typeof(Material), false) as Material;
            //var getMatInfo = GUILayout.Button("Material Info") && mMaterial != null;
            //EditorGUILayout.EndHorizontal();

            switch (shader_act)
            {
                //case ACT_GET_SHADER:
                //    var cmd = new GetShadersForSortCmd(this);
                //    sCurrentCmd = cmd.Schedule(true);
                //    break;
                case ACT_SORT_MATERIAL:
                    var sortCmd = new SortShadersCmd(this);
                    sCurrentCmd = sortCmd.Schedule(true);
                    break;
                case ACT_LOAD_MATERIAL:
                    var strip = new LocalMaterialWithAllVariantsCmd(this);
                    sCurrentCmd = strip.Schedule(true);
                    break;
                case ACT_COLLECT_VARIANT:
                    var variants = new CollectShaderVariants(this);
                    sCurrentCmd = variants.Schedule(true);
                    break;
                case ACT_CLEAR_INVALID_PROPS:
                    var cleaner = new ClearInvalidMaterialProp(this);
                    sCurrentCmd = cleaner.Schedule(true);
                    break;
                default:
                    break;
            }

            //if (getShaderInfo)
            //{
            //    GetShaderInfo();
            //}
            //if (getMatInfo)
            //{
            //    GetMaterialInfo();
            //}

            if (!string.IsNullOrEmpty(mShaderInfo))
            {
                EditorGUILayout.BeginVertical("helpbox");
                EditorGUILayout.SelectableLabel(mShaderInfo, GUILayout.MaxHeight(400));
                EditorGUILayout.EndVertical();
            }
        }

        void GetShaderInfo()
        {
            var buf = ParallelUtils.GetBuilder();
            buf.Append("Shader: \"").Append(mShader.name).Append("\"");

            var tag = mShader.FindSubshaderTagValue(0, new ShaderTagId("RenderPipeline"));
            buf.Append("\n\tPipeline: ").Append(tag.name);
            var light = mShader.FindPassTagValue(0, new ShaderTagId("LightMode"));
            buf.Append("\n\tLightMode: ").Append(light.name);

            for (int i = 0; i < m_KeepShaderVairants.Length; i++)
            {
                buf.Append("\n\tKeywords:");
                var keys = m_KeepShaderVairants[i].Split(' ');
                for (int k = 0; k < keys.Length; k++)
                {
                    var keyword = keys[k].Trim();
                    if (string.IsNullOrEmpty(keyword))
                        continue;
                    var valid = IsValidKeyword(mShader, keyword);
                    if (valid)
                        buf.Append(" (valid)").Append(keyword);
                    else
                        buf.Append(" (invalid)").Append(keyword);
                }
            }
            mShaderInfo = ParallelUtils.ReturnRelease(buf);
        }

        void GetMaterialInfo()
        {
            if (mMaterial == null)
                return;
            var shader = mMaterial.shader;
            var buf = ParallelUtils.GetBuilder();
            buf.Append("Shader: \"").Append(shader == null ? "<none>" : shader.name).Append("\"");
            var ser = new SerializedObject(mMaterial);
            var iter = ser.GetIterator();
            while (iter.Next(true))
            {
                buf.Append("\n\t").Append(iter.propertyPath).Append(":: ").Append(iter.propertyType);
            }
            mShaderInfo = buf.ToString();
            Debug.Log(mShaderInfo);
            CleanMaterial(mMaterial);
        }

        [MenuItem("CONTEXT/Material/Clear Invalid Properties")]
        static void ClearInvalidMaterialProperties(MenuCommand cmd)
        {
            var mat = cmd.context as Material;
            CleanMaterial(mat);
        }

        public static void CleanMaterial(Material mat)
        {
            if (mat == null)
                return;
            var shader = mat.shader;
            var names = new HashSet<string>();
            for (int i = ShaderUtil.GetPropertyCount(shader) - 1; i >= 0; i--)
            {
                var desc = ShaderUtil.GetPropertyDescription(shader, i);
                if (!string.IsNullOrEmpty(desc))
                    names.Add(ShaderUtil.GetPropertyName(shader, i));
            }
            var removed = new HashSet<string>();
            var ser = new SerializedObject(mat);
            var texs = ser.FindProperty("m_SavedProperties.m_TexEnvs");
            for (int i = texs.arraySize - 1; i >= 0; i--)
            {
                var item = texs.GetArrayElementAtIndex(i);
                var pname = item.FindPropertyRelative("first").stringValue;
                if (!names.Contains(pname))
                {
                    removed.Add(pname);
                    texs.DeleteArrayElementAtIndex(i);
                }
            }
            var ints = ser.FindProperty("m_SavedProperties.m_Ints");
            for (int i = ints.arraySize - 1; i >= 0; i--)
            {
                var item = ints.GetArrayElementAtIndex(i);
                var pname = item.FindPropertyRelative("first").stringValue;
                if (!names.Contains(pname))
                {
                    removed.Add(pname);
                    ints.DeleteArrayElementAtIndex(i);
                }
            }
            var floats = ser.FindProperty("m_SavedProperties.m_Floats");
            for (int i = floats.arraySize - 1; i >= 0; i--)
            {
                var item = floats.GetArrayElementAtIndex(i);
                var pname = item.FindPropertyRelative("first").stringValue;
                if (!names.Contains(pname))
                {
                    removed.Add(pname);
                    floats.DeleteArrayElementAtIndex(i);
                }
            }
            var colors = ser.FindProperty("m_SavedProperties.m_Colors");
            for (int i = colors.arraySize - 1; i >= 0; i--)
            {
                var item = colors.GetArrayElementAtIndex(i);
                var pname = item.FindPropertyRelative("first").stringValue;
                if (!names.Contains(pname))
                {
                    removed.Add(pname);
                    colors.DeleteArrayElementAtIndex(i);
                }
            }
            var invalidKeywords = ser.FindProperty("m_InvalidKeywords");
            invalidKeywords.arraySize = 0;
            if (removed.Count > 0)
            {
                Debug.LogFormat(mat, "\"{0}\":: Removed Invalid Properties: \n{1}", mat.name, ParallelUtils.Gather(removed.GetEnumerator()));
            }
            ser.ApplyModifiedProperties();
            ser.Dispose();
        }

        //[MenuItem("CONTEXT/ShaderVariantCollection/Strip Shaders Util/Get Used Variants", true)]
        //static bool CanGetVariants(MenuCommand cmd)
        //{
        //    return cmd.context is ShaderVariantCollection && !sCurrentCmd.IsValid;
        //}

        //[MenuItem("CONTEXT/ShaderVariantCollection/Strip Shaders Util/Get Used Variants")]
        //static void LoadUsedMaterials(MenuCommand cmd)
        //{
        //    var variants = cmd.context as ShaderVariantCollection;
        //    if (variants == null)
        //        return;
        //    sCurrentCmd.Abort();
        //    var strip = new LocalMaterialWithAllVariantsCmd(variants);
        //    sCurrentCmd = strip.Schedule(true);
        //}

        static readonly Regex OFF_KEYWORD_PATTERN = new Regex("^(_+|([a-z_0-9]*_off))$", RegexOptions.IgnoreCase);

        void GetPresetKeywords(KeywordCollection collection, Material mat)
        {
            var preset = new KeywordPreset(mat);
            if (mat != null)
            {
                var keywords = mat.enabledKeywords;
                for (int i = 0; i < keywords.Length; i++)
                {
                    var key = keywords[i];
                    if (key.isValid)
                        preset.keywords.Add(key.name);
                }
            }
            for (int i = 0; i < m_KeepShaderVairants.Length; i++)
            {
                var key = new KeywordGroup(m_KeepShaderVairants[i], collection.shader, preset.keywords);
                if (key.keywords.Length > 0)
                    preset.noStripKeywords.Add(key);
            }
            collection.presets.Add(preset);
        }

        void StripShaderVariantsCombination(KeywordCollection collection)
        {
            var heap = new MinHeap<string>(16, (str1, str2) => str1.CompareTo(str2));
            collection.combinations.Clear();
            // strip same keywords

            for (int i = 0; i < collection.presets.Count; i++)
            {
                var preset = collection.presets[i];
                var keeps = preset.noStripKeywords.ToArray();
                int[] arr = new int[keeps.Length];
                for (int k = 0; k < arr.Length; k++)
                {
                    arr[k] = keeps[k].allowSwitchOff ? -1 : 0;
                }
                do
                {
                    var keywords = new KeywordCombination(preset);
                    keywords.isDefault = true;
                    for (int k = 0; k < arr.Length; k++)
                    {
                        var index = arr[k];
                        if (index >= 0)
                        {
                            keywords.moreKeywords.Add(keeps[k].keywords[index]);
                            keywords.isDefault = false;
                        }
                    }
                    keywords.CalculateCombinationID(heap);
                    if (!collection.combinations.ContainsKey(keywords.Id))
                    {
                        collection.combinations.Add(keywords.Id, keywords);
                    }
                    int delta = 1;
                    for (int k = arr.Length - 1; k >= 0; k--)
                    {
                        var v = arr[k] + delta;
                        var keep = keeps[k];
                        if (k > 0 && v >= keep.keywords.Length)
                        {
                            v = keep.allowSwitchOff ? -1 : 0;
                            delta = 1;
                            arr[k] = v;
                        }
                        else
                        {
                            arr[k] = v;
                            break;
                        }
                    }
                } while (arr.Length > 0 && arr[0] < keeps[0].keywords.Length);
            }

            var buf = ParallelUtils.GetBuilder();
            buf.Append("Shader \"").Append(collection.name).Append("\" keywords:\n");
            var n = 1;
            foreach (var comb in collection.combinations.Values)
            {
                buf.Append("\t#").Append(n++).Append(":");
                var index = buf.Length;
                foreach (var key in comb.preset.keywords)
                {
                    buf.Append(" ").Append(key);
                }
                foreach (var key in comb.moreKeywords)
                {
                    buf.Append(" ").Append(key);
                }
                if (index == buf.Length)
                    buf.Append(" DEFAULT");
                comb.title = buf.ToString(index, buf.Length - index);
                buf.Append("\n");
            }
            //Debug.Log(ParallelUtils.ReturnRelease(buf));
        }

        struct KeywordGroup
        {
            public readonly int id;
            public readonly string[] keywords;
            public readonly bool allowSwitchOff;

            public KeywordGroup(string keys, Shader validShader, HashSet<string> exists)
            {
                var space = keys.IndexOf(' ');
                if (space == -1)
                {
                    var valid = exists == null || !exists.Contains(keys);
                    if (valid)
                        valid = validShader == null || IsValidKeyword(validShader, keys);
                    if (valid)
                        this.keywords = new string[] { keys };
                    else
                        this.keywords = new string[0];
                    allowSwitchOff = true;
                }
                else
                {
                    allowSwitchOff = false;
                    var arr = keys.Split(' ');
                    int len = arr.Length;
                    for (int i = arr.Length - 1; i >= 0; i--)
                    {
                        var key = arr[i].Trim();
                        bool valid = !string.IsNullOrEmpty(key);
                        if (valid && OFF_KEYWORD_PATTERN.IsMatch(key))
                        {
                            valid = false;
                            allowSwitchOff = true;
                        }

                        if (valid)
                            valid = exists == null || !exists.Contains(key);
                        if (valid)
                            valid = validShader == null || IsValidKeyword(validShader, key);
                        if (!valid)
                        {
                            arr[i] = arr[len - 1];
                            arr[len - 1] = null;
                            len--;
                        }
                    }
                    if (len == arr.Length)
                    {
                        this.keywords = arr;
                    }
                    else
                    {
                        this.keywords = new string[len];
                        System.Array.Copy(arr, keywords, len);
                    }
                }
                id = 0;
                for (int i = 0; i < keywords.Length; i++)
                {
                    id = MathExt.Hash(id, keywords[i].GetHashCode());
                }
            }

            public override int GetHashCode()
            {
                return id;
            }

            public override bool Equals(object obj)
            {
                return obj is KeywordGroup && ((KeywordGroup)obj).id == id;
            }
        }

        class KeywordCombination: System.IEquatable<KeywordCombination>
        {
            int id;
            internal readonly KeywordPreset preset;
            internal bool isDefault;
            internal readonly List<string> moreKeywords;
            internal string title;
            public int Id { get { return id; } }
            public KeywordCombination(KeywordPreset preset)
            {
                moreKeywords = new List<string>();
                this.preset = preset;
            }

            public bool ContainsKeyword(string keyword)
            {
                if (preset.keywords.Contains(keyword))
                    return true;
                if (moreKeywords.Contains(keyword))
                    return true;
                return false;
            }

            public Material CreateMaterial(List<string> keywordsCache)
            {
                if (isDefault || preset.material == null)
                    return preset.material;
                var mat = Object.Instantiate(preset.material);
                mat.name = ParallelUtils.Concat(preset.material.name, "_variant");
                //foreach (var key in moreKeywords)
                //{
                //    mat.EnableKeyword(new LocalKeyword(shader, key));
                //}
                keywordsCache.Clear();
                keywordsCache.AddRange(preset.keywords);
                keywordsCache.AddRange(moreKeywords);
                var keywords = keywordsCache.ToArray();
                // urp 有时候立即设置 shaderKeywords 无法生效，所以采用延时设置的方法
                ParallelUtils.Schedule(() =>
                {
                    mat.shaderKeywords = keywords;
                    EditorUtility.SetDirty(mat);
                }, true);
                //mat.shaderKeywords = keywordsCache.ToArray();
                mat.hideFlags |= HideFlags.DontSave;
                return mat;
            }

            public void CalculateCombinationID(MinHeap<string> cache)
            {
                cache.Clear();
                foreach (var key in moreKeywords)
                {
                    cache.Add(key);
                }
                foreach (var key in preset.keywords)
                {
                    cache.Add(key);
                }
                id = 0;
                while (cache.Count > 0)
                {
                    id = MathExt.Hash(id, cache.RemoveTop().GetHashCode());
                }
            }

            public override int GetHashCode()
            {
                return id;
            }

            public override bool Equals(object obj)
            {
                return Equals(obj as KeywordCombination);
            }

            public bool Equals(KeywordCombination other)
            {
                if (other == null || id != other.id)
                    return false;
                var len1 = moreKeywords.Count + preset.keywords.Count;
                var len2 = other.moreKeywords.Count + other.preset.keywords.Count;
                if (len1 != len2)
                    return false;
                for (int i = 0; i < moreKeywords.Count; i++)
                {
                    if (!other.ContainsKeyword(moreKeywords[i]))
                        return false;
                }
                foreach (var key in preset.keywords)
                {
                    if (!other.ContainsKeyword(key))
                        return false;
                }
                return true;
            }
        }

        // keywords those defined in material
        class KeywordPreset
        {
            internal int keywordHashCode;
            public readonly HashSet<KeywordGroup> noStripKeywords;
            public readonly HashSet<string> keywords;
            public readonly Material material;
            public KeywordPreset(Material mat)
            {
                material = mat;
                noStripKeywords = new HashSet<KeywordGroup>();
                keywords = new HashSet<string>();
            }

            public void CalculateKeywordHashCode(MinHeap<string> heap)
            {
                heap.Clear();
                foreach (var key in keywords)
                {
                    heap.Add(key);
                }
                var id = 0;
                while (heap.Count > 0)
                {
                    id = MathExt.Hash(id, heap.RemoveTop().GetHashCode());
                }
                keywordHashCode = id;
            }
        }

        class KeywordCollection
        {
            public readonly string name;
            public readonly Shader shader;
            //public readonly List<KeywordGroup> keywords;
            public readonly List<KeywordPreset> presets;
            public readonly Dictionary<int, KeywordCombination> combinations;

            public KeywordCollection(Shader shader)
            {
                this.shader = shader;
                this.name = shader.name;
                //this.keywords = new List<KeywordGroup>();
                this.presets = new List<KeywordPreset>();
                combinations = new Dictionary<int, KeywordCombination>();
            }

            //bool ContainsKeyword(string keyword)
            //{
            //    for (int i = 0; i < presets.Count; i++)
            //    {
            //        var pres = presets[i];
            //        if (pres.keywords.Contains(keyword))
            //            return true;
            //        foreach (var kep in pres.keepKeywords)
            //        {
            //            if (kep.keywords.Contains(keyword))
            //                return true;
            //        }
            //    }
            //    return false;
            //}

            public int GetAllVariants(StripShadersUtil cfg, string usingPipeline, ShaderVariantCollection collection)
            {
                if (!string.IsNullOrEmpty(usingPipeline))
                {
                    var pipeline = shader.FindSubshaderTagValue(0, new ShaderTagId("RenderPipeline")).name;
                    if (pipeline != usingPipeline && !string.IsNullOrEmpty(pipeline))
                    {
                        Debug.LogErrorFormat("Missmatch RenderPipeline. (shader: {0}", shader.name);
                        return 0;
                    }
                }

                //Debug.LogFormat("[Collect Shader] {0}", shader.name);
                //var sdata = ShaderUtil.GetShaderData(shader);
                //var subShaderNum = sdata.SubshaderCount;
                var keywords = new List<string>();
                //for (int subShaderId = 0; usePass.Count > 0 && subShaderId < subShaderNum; subShaderId++)
                //{
                //    var subShader = sdata.GetSubshader(subShaderId);
                //var passNum = subShader.PassCount;
                var passNum = shader.passCount;
                int num = 0;
                for (int passId = 0; passId < passNum; passId++)
                {
                    //var pass = subShader.GetPass(passId);
                    var lightMod = shader.FindPassTagValue(passId, new ShaderTagId("LightMode"));
                    //pass.FindTagValue(new ShaderTagId("LightMode"));
                    var passType = cfg.GetPassType(lightMod.name);
                    if (passType == Invalid_PassType || (cfg.m_CollectPassTypes & (1 << (int)passType)) == 0)
                        continue;

                    var passIdentify = GetPassID(/*(uint)subShaderId,*/ (uint)passId);
                    var variants = ShaderUtil.GetPassKeywords(shader, passIdentify);
                    foreach (var comb in combinations.Values)
                    {
                        keywords.Clear();
                        foreach (var keyword in variants)
                        {
                            if (keyword.isValid && keyword.type == ShaderKeywordType.UserDefined && comb.ContainsKeyword(keyword.name))
                                keywords.Add(keyword.name);
                        }
                        try
                        {
                            collection.Add(new ShaderVariantCollection.ShaderVariant(shader, passType, keywords.ToArray()));
                            num++;
                        }
                        catch (System.Exception e)
                        {
                            //Debug.LogException(e);
                            Debug.LogErrorFormat("Add vairants \"{0}\" for shader \"{1}\"#{2} error. (PassType: {3} LightMode: {4})\n{5}",
                                ParallelUtils.Gather(keywords.GetEnumerator(), " "), shader.name, passId, passType, lightMod.name, e.Message);
                        }
                    }
                }
                return num;
                //}
            }

        }

        //class GetShadersForSortCmd : IYieldCommand
        //{
        //    StripShadersUtil cfg;
        //    public GetShadersForSortCmd(StripShadersUtil cfg)
        //    {
        //        this.cfg = cfg;
        //    }
        //    public IEnumerator Execute()
        //    {
        //        Debug.Assert(cfg != null, "Don't find project cfg: Strip Shaders Util");
        //        yield return null;
        //        var shaders = AssetDatabase.FindAssets("t:shader", new string[] { cfg.m_SortShaderFolder });
        //        var dirty = false;
        //        foreach (var guid in shaders)
        //        {
        //            var path = AssetDatabase.GUIDToAssetPath(guid);
        //            var shader = AssetDatabase.LoadAssetAtPath<Shader>(path);
        //            if (shader == null)
        //                continue;
        //            if (!cfg.m_SortShaders.Contains(shader))
        //            {
        //                dirty = true;
        //                cfg.m_SortShaders.Add(shader);
        //            }
        //        }
        //        if (dirty)
        //            cfg.SetDirty();
        //    }
        //}

        class SortShadersCmd : IYieldCommand, IBreakCommandSignal
        {
            StripShadersUtil cfg;

            struct SortMaterial
            {
                public int orderId;
                public Material material;
                public SortMaterial(Material mat, int orderId)
                {
                    this.material = mat;
                    this.orderId = orderId;
                }
            }

            int GetShaderIndex(Shader shader)
            {
                for (int i = 0; i < cfg.m_SortingShaders.Length; i++)
                {
                    if (cfg.m_SortingShaders[i] == shader)
                        return i;
                }
                return -1;
            }

            SortMaterial GetMaterialSortID(Material mat, MinHeap<string> keywordsCache)
            {
                int index = GetShaderIndex(mat.shader);
                if (index == -1)
                    return default;
                keywordsCache.Clear();
                var keywords = mat.enabledKeywords;
                for (int i = 0; i < keywords.Length; i++)
                {
                    var key = keywords[i];
                    if (key.isValid && key.type == ShaderKeywordType.UserDefined)
                        keywordsCache.Add(key.name);
                }
                int id = 0;
                while (keywordsCache.Count > 0)
                {
                    id = MathExt.Hash(id, keywordsCache.RemoveTop().GetHashCode());
                }
                SortMaterial sort;
                sort.material = mat;
                sort.orderId = ((index & 0xff) << 24) | (id & 0xffffff);
                return sort;
            }

            public SortShadersCmd(StripShadersUtil cfg)
            {
                this.cfg = cfg;
            }

            public IEnumerator Execute()
            {
                Debug.Assert(cfg != null, "Don't find project cfg: Strip Shaders Util");
                var snum = cfg.m_SortingShaders == null ? 0 : cfg.m_SortingShaders.Length;
                if (snum == 0)
                    yield break;
                AssetDatabase.StartAssetEditing();
                //var sortStart = Mathf.Min(cfg.m_SortingShaderOffset, 99 - snum);
                var cancel = EditorUtility.DisplayCancelableProgressBar("Sort Materials", "Find loaded materials...", 0);
                if (cancel)
                {
                    OnBreak();
                    yield break;
                }
                MinHeap<SortMaterial> materials = new MinHeap<SortMaterial>(1024, (a, b) => a.orderId < b.orderId ? -1 : (a.orderId > b.orderId ? 1 : 0));
                MinHeap<string> keywordsCache = new MinHeap<string>(8, (k) => k.GetHashCode());

                var assets = AssetDatabase.FindAssets("t:material",
                   cfg.m_MaterialFolders == null || cfg.m_MaterialFolders.Length == 0 ? cfg.mDefaultFolders : cfg.m_MaterialFolders);
                var f = 1f / (assets.Length + 1);
                int num = 0;
                foreach(var guid in assets)
                {
                    num++;
                    var path = AssetDatabase.GUIDToAssetPath(guid);
                    if (string.IsNullOrEmpty(path) || !cfg.ImportMaterial(path))
                        continue;
                    cancel = EditorUtility.DisplayCancelableProgressBar("Sort Materials", path, num * f);
                    if (cancel)
                    {
                        OnBreak();
                        yield break;
                    }
                    var mat = AssetDatabase.LoadAssetAtPath<Material>(path);
                    if (mat == null)
                        continue;
                    var sortm = GetMaterialSortID(mat, keywordsCache);
                    if (sortm.material == null)
                        continue;
                    materials.Add(sortm);

                    //var index = GetShaderIndex(mat.shader);
                    //if (index == -1)
                    //    continue;
                    //var dirty = !Mathf.Approximately(0, mat.GetFloat("_QueueControl")) ||
                    //    !Mathf.Approximately(index + sortStart, mat.GetFloat("_QueueOffset"));
                    //mat.SetFloat("_QueueControl", 0);
                    //mat.SetFloat("_QueueOffset", index + sortStart);
                    //if (dirty)
                    //    EditorUtility.SetDirty(mat);
                    //Debug.LogFormat(mat, "Set \"{0}\"'s RenderQ: {1}", mat.name, index + sortStart);
                }
                if (materials.Count > 0)
                {
                    var sortId = materials.TopValule.orderId;
                    var sortIndex = 0;
                    List<SortMaterial> sortMaterials = new List<SortMaterial>(materials.Count);
                    while (materials.Count > 0)
                    {
                        var mat = materials.RemoveTop();
                        if (sortId != mat.orderId)
                        {
                            sortId = mat.orderId;
                            sortIndex++;
                        }
                        mat.orderId = sortIndex;
                        sortMaterials.Add(mat);

                    }
                    EditorUtility.DisplayProgressBar("Sort Materials", "Fix RenderQ offset...", 0.9f);
                    var shaderCount = cfg.m_SortingShaders.Length;
                    var delta = (int)sortMaterials[sortMaterials.Count - 1].orderId - 100;
                    if(delta > 0)
                    {
                        var d = Mathf.Max(1, 100 / shaderCount);
                        var baseOrder = (sortMaterials.Count - 1) * d;
                        var shader = sortMaterials[sortMaterials.Count - 1].material.shader;
                        for (int i = sortMaterials.Count - 1; i >= 0; i--)
                        {
                            var mat = sortMaterials[i];
                            if (mat.material.shader != shader)
                            {
                                shader = mat.material.shader;
                                baseOrder -= d;
                            }
                            mat.orderId = Mathf.Max(baseOrder, mat.orderId - delta);
                            sortMaterials[i] = mat;
                        }
                    }

                    var sortStart = Mathf.Min(cfg.m_SortingShaderOffset, 50 - sortMaterials[sortMaterials.Count - 1].orderId);
                    for (int i = 0; i < sortMaterials.Count; i++)
                    {
                        var mat = sortMaterials[i];
                        var renderQ = mat.orderId + sortStart;
                        var dirty = !Mathf.Approximately(0, mat.material.GetFloat("_QueueControl")) ||
                        !Mathf.Approximately(renderQ, mat.material.GetFloat("_QueueOffset"));
                        mat.material.SetFloat("_QueueControl", 0);
                        mat.material.SetFloat("_QueueOffset", renderQ);
                        if (dirty)
                        {
                            EditorUtility.SetDirty(mat.material);
                            Debug.LogFormat(mat.material, "Set \"{0}\"'s RenderQ: {1} (shader: {2})", mat.material.name, renderQ, mat.material.shader.name);
                        }
                    }
                }
                OnBreak();
            }

            public void OnBreak()
            {
                EditorUtility.ClearProgressBar();
                AssetDatabase.StopAssetEditing();
            }
        }

        class LocalMaterialWithAllVariantsCmd : IYieldCommand, IBreakCommandSignal
        {
            //string shaderFolder;
            StripShadersUtil cfg;
            Dictionary<Shader, KeywordCollection> shaderCollections;

            public LocalMaterialWithAllVariantsCmd(StripShadersUtil cfg)
            {
                this.cfg = cfg;
                shaderCollections = new Dictionary<Shader, KeywordCollection>();
            }


            public IEnumerator Execute()
            {
                Debug.Assert(cfg != null, "Don't find project cfg: Strip Shaders Util");

                var cancel = EditorUtility.DisplayCancelableProgressBar("Get Used Variants", "Find materials...", 0f);
                if (cancel)
                {
                    OnBreak();
                    yield break;
                }
                yield return null;
                var assets = AssetDatabase.FindAssets("t:material",
                    cfg.m_MaterialFolders == null || cfg.m_MaterialFolders.Length == 0 ? cfg.mDefaultFolders : cfg.m_MaterialFolders);
                //HashSet<Shader> shaders = new HashSet<Shader>();
                var fac = 1f / (assets.Length + 1f);
                var progress = 0;
                foreach (var guid in assets)
                {
                    var path = AssetDatabase.GUIDToAssetPath(guid);
                    if (!cfg.ImportMaterial(path))
                        continue;
                    cancel = EditorUtility.DisplayCancelableProgressBar("Get Used Variants", "Find materials...", progress++ * fac);
                    if (cancel)
                    {
                        OnBreak();
                        yield break;
                    }
                    var mat = AssetDatabase.LoadAssetAtPath<Material>(path);
                    var shader = mat == null ? null : mat.shader;
                    if (shader == null || !cfg.ImportShader(shader.name))
                        continue;
                    KeywordCollection collection;
                    if (!shaderCollections.TryGetValue(shader, out collection))
                    {
                        collection = new KeywordCollection(shader);
                        shaderCollections.Add(shader, collection);
                    }
                    cfg.GetPresetKeywords(collection, mat);
                }

                var cmd = ParallelUtils.ScheduleParallel<KeywordCollection>(cfg.StripShaderVariantsCombination, shaderCollections.Values);
                while (cmd.IsAlive)
                {
                    cancel = EditorUtility.DisplayCancelableProgressBar("Get Used Variants", "Strip same variants...", cmd.GetExecution().progress);
                    if (cancel)
                    {
                        OnBreak();
                        yield break;
                    }
                    else
                    {
                        yield return null;
                    }
                }
                int count = 0;
                foreach (var collection in shaderCollections.Values)
                {
                    count += collection.combinations.Count;
                }
                if (count == 0)
                {
                    OnBreak();
                    yield break;
                }

                var cols = Mathf.FloorToInt(Mathf.Sqrt(count));
                if (cols * cols < count)
                    cols++;
                var root = GameObject.Find("USING MATERIALS");
                if (root == null)
                {
                    root = new GameObject("USING MATERIALS");
                }
                Vector3 offset = new Vector3(-cols * 0.5f, 0, -cols * 0.5f);
                int num = 0;
                fac = 1f / count;
                var keywordLst = new List<string>();
                foreach (var collection in shaderCollections.Values)
                {
                    var shader = collection.shader.name.Replace("/", "_");
                    var trans = root.transform.Find(shader);
                    if (trans == null)
                    {
                        var go = new GameObject(shader);
                        trans = go.transform;
                        trans.parent = root.transform;
                        trans.localPosition = Vector3.zero;
                        trans.localRotation = Quaternion.identity;
                        trans.localScale = Vector3.one;
                    }
                    foreach (var comb in collection.combinations.Values)
                    {
                        cancel = EditorUtility.DisplayCancelableProgressBar("Get Used Variants", comb.title, num * fac);
                        if (cancel)
                        {
                            OnBreak();
                            yield break;
                        }
                        var item = trans.Find(comb.title);
                        if (item == null)
                        {
                            var go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
                            go.name = comb.title;
                            item = go.transform;
                            item.parent = trans;
                            item.localPosition = offset + new Vector3(num % cols, 0, num / cols);
                            item.localRotation = Quaternion.identity;
                            item.localScale = Vector3.one;
                        }
                        var renderer = item.GetComponent<Renderer>();
                        if (renderer != null)
                        {
                            renderer.sharedMaterial = comb.CreateMaterial(keywordLst);
                        }
                        num++;
                    }
                }
                EditorUtility.ClearProgressBar();
            }

            public void OnBreak()
            {
                if (ParallelDispatcher.IsMainThread)
                    EditorUtility.ClearProgressBar();
                else
                    ParallelUtils.Schedule(EditorUtility.ClearProgressBar, true);
            }


        }

        class CollectShaderVariants : IYieldCommand, IBreakCommandSignal
        {
            StripShadersUtil cfg;
            Dictionary<Shader, KeywordCollection> shaderCollections;

            internal CollectShaderVariants(StripShadersUtil cfg)
            {
                this.cfg = cfg;
                shaderCollections = new Dictionary<Shader, KeywordCollection>();
            }

            public IEnumerator Execute()
            {
                Debug.Assert(cfg != null, "Don't find project cfg: Strip Shaders Util");
                Debug.Assert(cfg.m_VariantCollection != null, "Don't find ShaderVariantsCollection reference.");
                var cancel = EditorUtility.DisplayCancelableProgressBar("Get Used Variants", "Find materials...", 0f);
                if (cancel)
                {
                    OnBreak();
                    yield break;
                }
                yield return null;
                var assets = AssetDatabase.FindAssets("t:material",
                    cfg.m_MaterialFolders == null || cfg.m_MaterialFolders.Length == 0 ? cfg.mDefaultFolders : cfg.m_MaterialFolders);
                //HashSet<Shader> shaders = new HashSet<Shader>();
                var fac = 1f / (assets.Length + 1f);
                var progress = 0;
                foreach (var guid in assets)
                {
                    var path = AssetDatabase.GUIDToAssetPath(guid);
                    if (!cfg.ImportMaterial(path))
                        continue;
                    cancel = EditorUtility.DisplayCancelableProgressBar("Get Used Variants", "Find materials...", progress++ * fac);
                    if (cancel)
                    {
                        OnBreak();
                        yield break;
                    }
                    var mat = AssetDatabase.LoadAssetAtPath<Material>(path);
                    var shader = mat == null ? null : mat.shader;
                    if (shader == null || !cfg.ImportShader(shader.name))
                    {
                        //Debug.LogFormat("Skip Material: {0}", path);
                        continue;
                    }
                    //var shaderPath = AssetDatabase.GetAssetPath(shader);
                    KeywordCollection collection;
                    if (!shaderCollections.TryGetValue(shader, out collection))
                    {
                        collection = new KeywordCollection(shader);
                        shaderCollections.Add(shader, collection);
                    }
                    cfg.GetPresetKeywords(collection, mat);
                }
                if (cfg.m_KeepShadersForBuild != null)
                {
                    for (int i = 0; i < cfg.m_KeepShadersForBuild.Length; i++)
                    {
                        var shader = cfg.m_KeepShadersForBuild[i];
                        if (shader == null)
                            continue;
                        KeywordCollection collection;
                        if (!shaderCollections.TryGetValue(shader, out collection))
                        {
                            collection = new KeywordCollection(shader);
                            shaderCollections.Add(shader, collection);
                            cfg.GetPresetKeywords(collection, null);
                        }
                    }
                }
                var cmd = ParallelUtils.ScheduleParallel<KeywordCollection>(cfg.StripShaderVariantsCombination, shaderCollections.Values);
                while (cmd.IsAlive)
                {
                    cancel = EditorUtility.DisplayCancelableProgressBar("Get Used Variants", "Strip variants combination...", cmd.GetExecution().progress);
                    if (cancel)
                    {
                        OnBreak();
                        yield break;
                    }
                    else
                    {
                        yield return null;
                    }
                }

                fac = 1f / (shaderCollections.Count + 1f);
                progress = 0;
                EditorUtility.DisplayCancelableProgressBar("Get Used Variants", "Collect Shader Variants...", 0);
                var variants = cfg.m_VariantCollection;
                variants.Clear();
                var pipeline = GetCurrentPipeline();

                cfg.InitPassMap();
                int variantsNum = 0, shadersNum = 0;
                foreach (var collection in shaderCollections.Values)
                {
                    cancel = EditorUtility.DisplayCancelableProgressBar("Get Used Variants", $"Collect Shader Variants ({collection.shader.name})...",
                        progress++ * fac);
                    if (cancel)
                    {
                        OnBreak();
                        yield break;
                    }
                    var num = collection.GetAllVariants(cfg, pipeline, variants);
                    if(num > 0)
                    {
                        variantsNum += num;
                        shadersNum++;
                    }
                }
                EditorUtility.ClearProgressBar();
                EditorUtility.SetDirty(variants);
                Debug.Log($"Find total {variantsNum} variants, {shadersNum} shaders.");
            }

            public void OnBreak()
            {
                if (ParallelDispatcher.IsMainThread)
                    EditorUtility.ClearProgressBar();
                else
                    ParallelUtils.Schedule(EditorUtility.ClearProgressBar, true);
            }
        }

        class FindUsingMaterialsCmd : ICommand
        {
            StripShadersUtil cfg;
            Shader contextShader;
            public FindUsingMaterialsCmd(Shader shader)
            {
                contextShader = shader;
            }

            void ICommand.Execute()
            {
                cfg = ProjectConfiguration.GetConfigurable<StripShadersUtil>();
                if (cfg == null)
                    return;
                var assets = AssetDatabase.FindAssets("t:material",
                    cfg.m_MaterialFolders == null || cfg.m_MaterialFolders.Length == 0 ? cfg.mDefaultFolders : cfg.m_MaterialFolders);
                foreach (var guid in assets)
                {
                    var path = AssetDatabase.GUIDToAssetPath(guid);
                    if (!cfg.ImportMaterial(path))
                        continue;
                    var mat = AssetDatabase.LoadAssetAtPath<Material>(path);
                    var shader = mat == null ? null : mat.shader;
                    if (shader == null || shader != contextShader)
                    {
                        //Debug.LogFormat("Skip Material: {0}", path);
                        continue;
                    }
                    Debug.Log(path, mat);
                }
            }
        }

        class ClearInvalidMaterialProp : ICommand
        {
            StripShadersUtil mCfg;
            internal ClearInvalidMaterialProp(StripShadersUtil cfg)
            {
                mCfg = cfg;
            }
            void ICommand.Execute()
            {
                string[] folders;
                if (mCfg.m_MaterialFolders == null || mCfg.m_MaterialFolders.Length == 0)
                    folders = new string[] { "Assets/" };
                else
                    folders = mCfg.m_MaterialFolders;
                AssetDatabase.StartAssetEditing();
                EditorUtility.DisplayProgressBar("Clear Invalid Mat Props", "Find Materials...", 0f);
                var mats = AssetDatabase.FindAssets("t:material", folders);
                var f = 1f / (mats.Length + 1);
                int num = 0;
                foreach (var guid in mats)
                {
                    num++;
                    var path = AssetDatabase.GUIDToAssetPath(guid);
                    EditorUtility.DisplayProgressBar("Clear Invalid Mat Props", path, num * f);
                    var mat = AssetDatabase.LoadAssetAtPath<Material>(path);
                    StripShadersUtil.CleanMaterial(mat);
                }

                AssetDatabase.StopAssetEditing();
                EditorUtility.ClearProgressBar();

            }
        }

        //class PrepareBuild: IPreprocessBuildWithReport
        //{

        //    public int callbackOrder { get { return -1; } }

        //    public void OnPreprocessBuild(BuildReport report)
        //    {
        //        var cfg = ProjectConfiguration.GetConfigurable<StripShadersUtil>();
        //        if (cfg != null)
        //        {
        //            cfg.GetAlwaysIncludeShaders();
        //            cfg.GetShadersForBuild(!MakeAB.IsBuildingAsset);
        //        }
        //    }
        //}

        class StripShaderBuild : IPreprocessShaders, IPreprocessBuildWithReport
        {
            public int callbackOrder { get { return -1; } }

            StripShadersUtil mCfg;
            StripShadersUtil GetCfg()
            {
                if (mCfg == null)
                {
                    mCfg = ProjectConfiguration.GetConfigurable<StripShadersUtil>();
                }
                return mCfg;
            }

            public void OnPreprocessBuild(BuildReport report)
            {
                PlayerSettings.preserveFramebufferAlpha = true;
                var cfg = GetCfg();
                if (cfg != null && cfg.m_AllowStripShaderBuild)
                {
                    var alwaysInclude = !IsBuildingAb;
                    if (alwaysInclude)
                        cfg.GetAlwaysIncludeShaders();
                    cfg.GetShadersForBuild(alwaysInclude);
                }
            }

            public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IList<ShaderCompilerData> data)
            {
                //if(shader.name == "Hidden/Internal-GUITexture")
                //{
                //    data.Clear();
                //    return;
                //}
                var cfg = GetCfg();
                // 鉴于某些隐藏 shader 动态调用，不剔除隐藏 shader
                if (cfg != null && cfg.m_AllowStripShaderBuild && !shader.name.StartsWithIgnoreCase("Hidden/"))
                {
                    if (!cfg.IsShaderForBuild(shader.name))
                    {
                        Debug.LogFormat("Strip Building Shader: {0}", shader.name);
                        data.Clear();
                        return;
                    }
                }
            }

        }

    }
}